#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import json
import os
import shutil
import stat
import uuid
import pexpect

from common.common import output_result_file, output_execution_result, check_path_legal, output_execution_result_ex, \
    read_tmp_json_file, get_host_sn, str_is_none_or_empty, execute_cmd_list
from common.common_models import ActionResult, SubJobDetails, LogDetail
from common.const import ParamConstant, ExecuteResultEnum, RepositoryName, SubJobStatusEnum, RepositoryDataTypeEnum, \
    CMDResult, ReportDBLabel, DBLogLevel
from common.parse_parafile import ParamFileUtil, get_env_variable
from common.file_common import check_file_exist, delete_file, create_dir_recursive, change_owner_by_name

from oracle import logger
from oracle.common.common import parse_backup_path, check_path_in_white_list, convert_error_code, \
    change_mount_nas_file_owner, get_instance_name_by_id, modify_instance_name_in_p_file
from oracle.common.constants import RootCommand, PluginPathConstant, ORACLEExecPower, \
    ORACLEJsonConstant, CacheRepoFilename, RestoreResultKey, ScriptExitCode, OracleDataBaseType, StartDataBase, \
    RecoverTargetType
from oracle.common.common import send_params_to_pexpect, exec_rc_tool_cmd
from oracle.common.constants import ErrorCode, OracleParentPath, OracleProgress
from oracle.common.user_env_common import update_oracle_env, get_user_shell_type


class OracleLiveMount:
    """
    oracle即时挂载
    """

    def __init__(self, pid, job_id, sub_job_id):
        self._pid = pid
        self._job_id = job_id
        self._sub_job_id = sub_job_id
        self._json_param = {}
        self.db_name = ""
        self.db_instance = ""
        self.db_user = ""
        self.db_user_pwd = ""
        self.asm_user = ""
        self.asm_user_pwd = ""
        self.channels = 4
        self.recover_target = "2"
        self.recover_path = ""
        self.recover_num = 1
        self.is_start_db = 1
        self.repo_paths = {}
        self.archive_repo = ""
        self.meta_data_path = ""
        self.cache_repo_path = ""
        self.recover_order = 1
        self.asm_sid_name = ""
        self.db_type = 0
        self.enc_algo = ""
        self.enc_key = ""
        self.p_file_pid = ""
        self.backup_repo = ""
        self.oracle_home = ""
        self.install_user_name = ""
        self.target_obj_inst_name = ""
        self.target_obj_sub_type = ""
        self.target_obj_id = ""
        self.fibre_channel = ""
        self.sub_job_name = ""
        self.mount_target_host_str = ""
        self.oracle_user = ""
        self.oracle_group = ""
        self.access_oracle_base = ""
        self.access_oracle_home = ""
        self.asm_install_user = ""
        self.copy_sub_type = ""
        self.bct_status = ""
        self.first_copy_protect_object_json = {}
        self.first_copy_protect_object_extend_info_json = {}
        self.response = ActionResult(code=ExecuteResultEnum.INTERNAL_ERROR.value)
        self.exit_status = ScriptExitCode.SUCCESS
        self.parse_params()

    def parse_params(self):
        params = ParamFileUtil.parse_param_file(self._pid)
        self._json_param = params
        job_json = self._json_param.get(ORACLEJsonConstant.JOB, {})
        copy_json = job_json.get(ORACLEJsonConstant.COPY, [{}])
        first_copy_json = copy_json[0]
        self.first_copy_protect_object_json = first_copy_json.get(ORACLEJsonConstant.PROTECT_OBJECT, {})
        self.db_name = self.first_copy_protect_object_json.get(ORACLEJsonConstant.NAME, "")
        self.copy_sub_type = self.first_copy_protect_object_json.get(ORACLEJsonConstant.SUB_TYPE, "")
        self.first_copy_protect_object_extend_info_json = self.first_copy_protect_object_json.get(
            ORACLEJsonConstant.EXTEND_INFO, {})
        self.get_instance_from_json(job_json)
        self.db_user = get_env_variable(f'job_copy_0_protectObject_auth_authKey_{self._pid}')
        self.db_user_pwd = get_env_variable(f'job_copy_0_protectObject_auth_authPwd_{self._pid}')
        self.asm_user = ""
        job_target_object_json = job_json.get(ORACLEJsonConstant.TARGET_OBJECT, {})
        self.asm_user_pwd = ""
        job_extend_info_json = job_json.get(ORACLEJsonConstant.EXTEND_INFO, {})
        if job_extend_info_json:
            self.channels = job_extend_info_json.get(ORACLEJsonConstant.CHANNELS, 1)
            # recover_target 0：恢复到原机原实例，2：恢复到新机
            self.recover_target = job_extend_info_json.get(ORACLEJsonConstant.RECOVER_TARGET, "2")
            self.recover_path = job_extend_info_json.get(ORACLEJsonConstant.RECOVER_PATH, "")
            self.recover_num = job_extend_info_json.get('hostNumber', 1)
            self.bct_status = job_extend_info_json.get("bctStatus", "false")
            # 是否启动db 0:不启动  1:启动
            self.is_start_db = job_extend_info_json.get('isStartDB', 1)
            self.fibre_channel = job_extend_info_json.get("fibreChannel", "")
            self.mount_target_host_str = job_extend_info_json.get("mount_target_host", "")
        self.repo_paths = parse_backup_path(first_copy_json.get(ORACLEJsonConstant.REPOSITORIES, [{}]))
        self.archive_repo = ""
        data_repo_path = self.repo_paths.get(RepositoryName.DATA_REPOSITORY)[0]
        if not self.repo_paths.get(RepositoryName.CACHE_REPOSITORY):
            self.cache_repo_path = os.path.join(data_repo_path, f'cache_{self._job_id}')
            create_dir_recursive(self.cache_repo_path)
        else:
            self.cache_repo_path = self.repo_paths.get(RepositoryName.CACHE_REPOSITORY)[0]

        # oracle目标恢复目录（主机上的目录路径）,不指定就是原位置
        self.recover_order = 1
        # ASM的SID名称
        self.asm_sid_name = job_target_object_json.get(ORACLEJsonConstant.EXTEND_INFO, {}).get("asmSidName", "")
        #  0：ASM类型磁盘，1：文件系统，sh脚本中未使用此变量
        self.db_type = job_target_object_json.get(ORACLEJsonConstant.EXTEND_INFO, {}).get("dbType", "")
        first_copy_extend_info = first_copy_json.get(ORACLEJsonConstant.EXTEND_INFO, {})
        # rman备份的时候的加密算法
        self.enc_algo = first_copy_extend_info.get(ORACLEJsonConstant.BACKUP_ALGO, "")
        # rman备份的时候的加密key，备份的时候生成，恢复的时候从copy信息中获取
        self.enc_key = first_copy_extend_info.get(ORACLEJsonConstant.BACKUP_ALGO_VALUE, "")
        # 插件生成一个uuid，用于临时保存p_file
        self.p_file_pid = ""
        self.target_obj_inst_name = job_target_object_json.get(ORACLEJsonConstant.EXTEND_INFO, {}).get("inst_name", "")
        self.target_obj_id = job_target_object_json.get("id", "")
        self.target_obj_sub_type = job_target_object_json.get(ORACLEJsonConstant.SUB_TYPE, "")
        self.sub_job_name = self._json_param.get("subJob", {}).get("jobName", "")
        self.oracle_user = job_target_object_json.get(ORACLEJsonConstant.EXTEND_INFO, {}). \
            get(ORACLEJsonConstant.INSTALL_USER_NAME, ORACLEExecPower.ORACLE_USER)
        self.oracle_group = job_target_object_json.get(ORACLEJsonConstant.EXTEND_INFO, {}). \
            get("oracle_group", ORACLEExecPower.ORACLE_GROUP)
        # 用于没有配置环境变量的场景
        self.get_oracle_env_param(job_target_object_json)

    def get_oracle_env_param(self, job_target_object_json):
        get_user_shell_type(self.oracle_user)
        self.access_oracle_base = job_target_object_json.get(ORACLEJsonConstant.EXTEND_INFO, {}). \
            get("accessOracleBase", "")
        self.access_oracle_home = job_target_object_json.get(ORACLEJsonConstant.EXTEND_INFO, {}). \
            get("accessOracleHome", "")
        update_oracle_env(oracle_base=self.access_oracle_base, oracle_home=self.access_oracle_home)

    def get_instance_from_json(self, job_json):
        self.db_instance = self.first_copy_protect_object_extend_info_json.get(ORACLEJsonConstant.INST_NAME, "")
        job_id = job_json.get('jobId', '')
        if job_id.endswith('_unmount'):
            extend_instances = job_json.get('copy', [])[0].get('extendInfo', {}).get('instances', '')
        else:
            extend_instances = job_json.get('extendInfo', {}).get('instances', '')
        instance_name = get_instance_name_by_id(extend_instances, job_json.get('targetObject', {}).get('id', ''))
        # 如果指定了实例名，设置指定的实例名
        if instance_name:
            self.db_instance = instance_name

    def get_asm_info(self):
        asm_info = self.first_copy_protect_object_json.get(ORACLEJsonConstant.AUTH, {}). \
            get(ORACLEJsonConstant.EXTEND_INFO, {}).get(ORACLEJsonConstant.ASM_INFO_, "")
        asm_info_dic = {}
        if not str_is_none_or_empty(asm_info):
            try:
                asm_info_dic = json.loads(asm_info)
            except json.JSONDecodeError:
                logger.error(f'json decode asm_info exception , job_id:{self._job_id}.')
                return False
        self.asm_user = asm_info_dic.get('authKey', "")
        self.asm_user_pwd = asm_info_dic.get('authPwd', "")
        self.asm_install_user = asm_info_dic.get('installUsername', "")

        return True

    def prepare_path(self, path):
        if os.path.exists(path):
            logger.info(f"Already exists path, pid:{self._pid} jobId:{self._job_id}.")
            return True
        try:
            create_dir_recursive(path)
        except Exception as e_file_exist:
            logger.error(f"Mkdir failed. pid:{self._pid} e_file_exist:{e_file_exist}. jobId:{self._job_id}.")
            return False
        try:
            os.chmod(path, stat.S_IRWXU)
        except Exception:
            logger.error(f"Chmod failed. pid:{self._pid} jobId:{self._job_id}.")
            return False
        return True

    def get_data_repository(self):
        job_json = self._json_param.get(ORACLEJsonConstant.JOB, {})
        if not job_json:
            return {}
        repository_json = job_json.get(ORACLEJsonConstant.COPY, [{}])[0]. \
            get(ORACLEJsonConstant.REPOSITORIES, [])
        repository_num = len(repository_json)
        for i in range(repository_num):
            if repository_json[i].get(ORACLEJsonConstant.REPOSITORY_TYPE, "") \
                    == RepositoryDataTypeEnum.DATA_REPOSITORY.value:
                return repository_json[i]
        return {}

    def mount_local_path(self):
        data_repository = self.get_data_repository()
        if not data_repository:
            logger.error(f"Get data repository failed. pid:{self._pid} jobId:{self._job_id}.")
            return False
        logger.info(f"Mount_local_path Target_obj_id:{self.target_obj_id} jobId:{self._job_id}.")
        path = os.path.join(ORACLEExecPower.ORACLE_LIVE_MOUNT_PATH, self.target_obj_id)
        logger.info(f"Mount_local_path path:{path} jobId:{self._job_id}.")
        self.unmount_local_path()
        ret = self.prepare_path(path)
        if not ret:
            return False
        # 调用挂载脚本
        ret = self.mount_by_script(path, data_repository)
        if not ret:
            return False
        self.backup_repo = path
        self.oracle_home = path
        return True

    def unmount_local_path(self):
        logger.info(f"Unmount_local_path target_obj_id:{self.target_obj_id} jobId:{self._job_id}.")
        path = os.path.join(ORACLEExecPower.ORACLE_LIVE_MOUNT_PATH, self.target_obj_id)
        logger.info(f"Unmount_local_path path:{path} jobId:{self._job_id}.")
        if not check_path_legal(path, OracleParentPath.TMP):
            logger.error(f"Path is unlawful. pid:{self._pid} jobId:{self._job_id}.")
            return False
        cmd_list = [f"mount", f"grep {path}"]
        return_code, out_info, _ = execute_cmd_list(cmd_list)
        if return_code == CMDResult.SUCCESS:
            # 调用卸载脚本
            data_repository = self.get_data_repository()
            if not data_repository:
                logger.error(f"Get data repository failed. pid:{self._pid} jobId:{self._job_id}.")
                return False
            path_array = [path]
            data_repository[ORACLEJsonConstant.PATH] = path_array
            ret = self.call_rc_tool_cmd("UnMountRepositoryByPlugin", data_repository)
            if not ret:
                logger.error(f"Unmount failed. pid:{self._pid} jobId:{self._job_id}.")
                return False
        else:
            logger.info(f"Mount not exist. pid:{self._pid} jobId:{self._job_id}.")
        # 清理本地目录，即时失败也不影响结果
        ret, _ = check_path_in_white_list(path)
        if not ret:
            logger.error(f"Invalid path.")
            return False
        self.clear_mount_path(path)
        return True

    def clear_mount_path(self, path):
        if not os.path.exists(path):
            logger.info(f"Path not exist. pid:{self._pid} jobId:{self._job_id}.")
            return True
        try:
            shutil.rmtree(path)
        except Exception as exception_str:
            logger.error(f"Rm tree failed. path: err:{exception_str} pid:{self._pid} jobId:{self._job_id}.")
            return False
        return True

    def mount_by_script(self, local_path, data_repository):
        # 调用挂载脚本执行挂载
        path = [local_path]
        data_repository[ORACLEJsonConstant.PATH] = path
        remote_host_array = data_repository.get(ORACLEJsonConstant.REMOTE_HOST, [])
        data_repository[ORACLEJsonConstant.REMOTE_HOST] = []
        if not remote_host_array:
            logger.error(f"Remote host array error. pid:{self._pid} jobId:{self._job_id}.")
            return False
        for remote_host in remote_host_array:
            data_repository[ORACLEJsonConstant.REMOTE_HOST].append(remote_host)
            ret = self.call_rc_tool_cmd("MountRepositoryByPlugin", data_repository)
            if ret:
                logger.info(f"Rc_tool mount success. pid:{self._pid} jobId:{self._job_id}.")
                return True
        logger.error(f"Rc_tool mount failed. pid:{self._pid} jobId:{self._job_id}.")
        return False

    def call_rc_tool_cmd(self, cmd, data_repository):
        json_str = {
            "user": self.oracle_user,
            "group": self.oracle_group,
            "fileMode": ORACLEExecPower.ORACLE_FILE_MODE
        }

        json_extend_info = {
            "appType": self.target_obj_sub_type
        }
        if self.fibre_channel != "":
            json_extend_info["fibreChannel"] = self.fibre_channel
        logger.info(f"AppType:{self.target_obj_sub_type} jobId:{self._job_id}.")
        param_json = {
            "repository": [data_repository],
            "permission": json_str,
            "extendInfo": json_extend_info
        }
        param_path = os.path.join(ParamConstant.PARAM_FILE_PATH, f"param_{self._job_id}")
        result_path = os.path.join(ParamConstant.RESULT_PATH, f"result_{self._job_id}")
        ret = self.create_param_file(param_path, param_json)
        if not ret:
            return False

        ret = exec_rc_tool_cmd(cmd, param_path, result_path)
        if not ret:
            logger.warning(f"Exec rc tool failed. pid:{self._pid} jobId:{self._job_id}.")
            return False
        ret, _ = check_path_in_white_list(param_path)
        if not ret:
            logger.error(f"Invalid param_path.")
            return False
        ret, _ = check_path_in_white_list(param_path)
        if not ret:
            logger.error(f"Invalid result_path.")
            return False
        try:
            os.remove(param_path)
        except Exception:
            # 删除参数文件失败 不认为命令执行失败
            logger.warning(f"Remove param path failed. pid:{self._pid} jobId:{self._job_id}.")
        try:
            os.remove(result_path)
        except Exception:
            # 删除参数文件失败 不认为命令执行失败
            logger.warning(f"Remove result path failed. pid:{self._pid} jobId:{self._job_id}.")
        return True

    def create_param_file(self, param_path, param_json):
        ret, _ = check_path_in_white_list(param_path)
        if not ret:
            logger.error(f"Invalid param_path.")
            return False
        if os.path.exists(param_path):
            try:
                os.remove(param_path)
            except Exception as exception_str:
                logger.error(f"Rm file failed. err:{exception_str} pid:{self._pid} jobId:{self._job_id}.")
                return False
        logger.info(f"Write:{param_json} to param_path. pid:{self._pid} jobId:{self._job_id}.")
        try:
            output_execution_result(param_path, param_json)
        except Exception:
            logger.error(f"Create file failed. path:param_path pid:{self._pid} jobId:{self._job_id}.")
            return False
        return True

    def oracle_live_mount(self):
        ret = self.live_mount()
        if ret:
            self.output_result_info_to_file(ExecuteResultEnum.SUCCESS.value, ErrorCode.SUCCESS.value,
                                            SubJobStatusEnum.COMPLETED.value, ScriptExitCode.SUCCESS)
        else:
            self.output_result_info_to_file(ExecuteResultEnum.INTERNAL_ERROR.value, ErrorCode.INTERNAL_ERROR.value,
                                            SubJobStatusEnum.FAILED.value, self.exit_status)

    def live_mount_progress(self):
        """
        查询任务进度
        """

        result_file = os.path.join(ParamConstant.RESULT_PATH,
                                   f'{CacheRepoFilename.LIVE_MOUNT_PROGRESS}_{self._sub_job_id}')
        result_data = read_tmp_json_file(result_file)
        task_status = result_data.get(RestoreResultKey.TASK_STATUS, SubJobStatusEnum.RUNNING.value)
        if task_status == SubJobStatusEnum.RUNNING.value:
            # 任务未完成时将进度设置为50%
            progress = OracleProgress.PROGRESS_FIFTY
        else:
            logger.info(f'Live mount job end, task_status:{task_status}, job_id:{self._job_id}, '
                        f'sub_job_id:{self._sub_job_id}.')
            progress = OracleProgress.PROGRESS_ONE_HUNDRED
            delete_file(result_file)

        sub_job_detail = SubJobDetails(taskId=self._job_id, subTaskId=self._sub_job_id, progress=progress,
                                       taskStatus=task_status)

        if task_status == SubJobStatusEnum.FAILED:
            error_code = result_data.get(RestoreResultKey.ERROR_CODE, "")
            contents = result_data.get(RestoreResultKey.ERROR_PARAM, "")
            log_detail = LogDetail(logInfo=ReportDBLabel.LIVE_MOUNT_FAILED, logInfoParam=[self._sub_job_id],
                                   logLevel=DBLogLevel.ERROR.value, logDetail=error_code, logDetailInfo=[contents])
            sub_job_detail.extend_info = {'nodeId': get_host_sn()}
            sub_job_detail.log_detail = [log_detail]
        output_result_file(self._pid, sub_job_detail.dict(by_alias=True))

    def set_cluster_to_single_db_info(self):
        if self.copy_sub_type == OracleDataBaseType.ORACLE_CLUSTER and \
                self.target_obj_sub_type == OracleDataBaseType.ORACLE:
            node_info_list_protect = json.loads(self.first_copy_protect_object_extend_info_json.get('instances', ""))
            self.db_instance = node_info_list_protect[0].get('inst_name', '')
            # 如果指定了实例名，设置指定的实例名
            instance_name = get_instance_name_by_id(
                self._json_param.get('job', {}).get('extendInfo', {}).get('instances', ''),
                self._json_param.get('job', {}).get('targetObject', {}).get('id', ''))
            if instance_name:
                self.db_instance = instance_name
            self.oracle_home = node_info_list_protect[0].get('oracle_home', '')
            asm_info = json.loads(node_info_list_protect[0].get('asm_info', '{}'))
            self.asm_sid_name = asm_info.get('instName', '')

    def live_mount(self):
        logger.info(f"Init param success pid:{self._pid} jobid:{self._job_id}")
        if self.target_obj_sub_type != OracleDataBaseType.ORACLE_CLUSTER or (
                self.target_obj_sub_type == OracleDataBaseType.ORACLE_CLUSTER and
                self.sub_job_name == "live_mount_cluster_first"):
            logger.info(f"Enter mount_local_path .jobid:{self._job_id}")
            ret = self.mount_local_path()
            self.set_cluster_to_single_db_info()
            if not ret:
                self.unmount_local_path()
                logger.error(f"Mount local path failed. pid:{self._pid} jobid:{self._job_id}.")
                return False
            logger.info(f"Mount local path success. pid:{self._pid} jobid:{self._job_id}.")
            change_mount_nas_file_owner(
                **{"data_repo_path": self.backup_repo, "oracle_user": self.oracle_user,
                   "oracle_group": self.oracle_group,
                   "log_repo_path": None, "job_id": self._job_id})
            if (self.target_obj_sub_type == OracleDataBaseType.ORACLE_CLUSTER and
                    self.sub_job_name == "live_mount_cluster_first"):
                return True
        elif self.target_obj_sub_type == OracleDataBaseType.ORACLE_CLUSTER and \
                self.sub_job_name == "live_mount_cluster_second":
            path = os.path.join(ORACLEExecPower.ORACLE_LIVE_MOUNT_PATH, self.target_obj_id)
            self._parse_rac_node_info()
            self.backup_repo = path
            self.oracle_home = path
        elif self.target_obj_sub_type == OracleDataBaseType.ORACLE_CLUSTER and \
                self.sub_job_name == "live_mount_cluster_third":
            if self.is_start_db == StartDataBase.NO:
                return True
            self.recover_order = 2
            path = os.path.join(ORACLEExecPower.ORACLE_LIVE_MOUNT_PATH, self.target_obj_id)
            self._parse_rac_node_info()
            self.backup_repo = path
            self.oracle_home = path
        try:
            ret = self.live_mount_exe_shell()
        except Exception as exception_str:
            logger.error(f"Fun live mount exception. exe_str:{str(exception_str)} "
                         f" pid:{self._pid} jobId:{self._job_id}.")
            ret = False
        if not ret:
            # 任务失败之后，需要卸载
            self.unmount_local_path()
            return False
        logger.info(f"Live Mount success. pid:{self._pid} jobid:{self._job_id}.")
        return True

    def live_mount_exe_shell(self):
        """
        执行即时挂载shell脚本
        """
        if self.recover_target == RecoverTargetType.OTHER.value:
            logger.info(f'Live mount to other node, job_id:{self._job_id}, sub_job_id:{self._sub_job_id}.')
            self.create_live_mount_p_file()
        self.get_asm_info()
        script_param = self.get_live_mount_script_param()
        # 调用即时挂载脚本
        child = pexpect.spawn(
            f"sh {PluginPathConstant.SCRIPTS_PATH}/{RootCommand.NATIVE_LIVE_MOUNT.value}"
            f" {PluginPathConstant.SCRIPTS_PATH.value} {self._sub_job_id} {len(script_param)}",
            encoding='utf-8')

        send_params_to_pexpect(script_param, child)
        child.timeout = None
        child.expect(pexpect.EOF)
        child.close()
        self.exit_status = child.exitstatus
        logger.info(f'Live mount child.exitstatus:{self.exit_status}. job_id:{self._job_id}')
        if self.exit_status != ScriptExitCode.SUCCESS:
            logger.error(f"Live_mount timeout pid:{self._pid}. job_id:{self._job_id}")
            result = False
        else:
            logger.info(f"Live_mount success. job_id:{self._job_id}")
            result = True
        return result

    def create_live_mount_p_file(self):
        logger.info(f'Start creating p_file, job_id:{self._job_id}, sub_job_id:{self._sub_job_id}.')
        self.p_file_pid = str(uuid.uuid4())
        p_file_uuid = self.p_file_pid
        p_file_name = f'pfile{p_file_uuid}'
        p_file_path = f'{PluginPathConstant.NONE_ROOT_TMP_PATH.value}/{p_file_name}'
        modes = stat.S_IWUSR | stat.S_IRUSR | stat.S_IXUSR
        params = json.loads(self.mount_target_host_str)
        instances = self._json_param.get('job', {}).get('extendInfo', {}).get('instances', '')
        res_params = modify_instance_name_in_p_file(
            instances, params, self._json_param.get('job', {}).get('targetObject', {}).get('id', ''))
        flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
        with os.fdopen(os.open(p_file_path, flags, modes), 'w+') as out_file:
            for key, value in res_params.items():
                out_file.write(f'{key}={value}\n')
        try:
            logger.info(f'change owner by name, job id: {self._job_id}')
            change_owner_by_name(p_file_path, self.oracle_user, self.oracle_group)
        except Exception as e_change_owner:
            msg = f'Change p_file owner to {self.oracle_user}:{self.oracle_group} failed.'
            logger.error(f'{msg}, exception:{e_change_owner}, job_id:{self._job_id}, sub_job_id:{self._sub_job_id}.')

        logger.info(f'Create tmp p_file successfully, job_id:{self._job_id}, sub_job_id:{self._sub_job_id}.')

    def output_result_info_to_file(self, code, body_err, execution_status, exit_status,
                                   file_name=CacheRepoFilename.LIVE_MOUNT_PROGRESS):
        self.response.code = code
        self.response.body_err = body_err
        result_file = os.path.join(ParamConstant.RESULT_PATH,
                                   f'{file_name}_{self._sub_job_id}')
        if check_file_exist(result_file):
            logger.info(
                f'Remove progress flag:{result_file}, '
                f'job_id:{self._job_id}, sub_job_id:{self._sub_job_id}.')
            delete_file(result_file)
        error_code, contents = convert_error_code(self._job_id, self._sub_job_id, exit_status)
        output_execution_result_ex(result_file, {RestoreResultKey.TASK_STATUS: execution_status,
                                                 RestoreResultKey.ERROR_CODE: error_code,
                                                 RestoreResultKey.ERROR_PARAM: contents})
        logger.info(f"Output_result_info_to_file. code:{code}. body_err:{body_err}. "
                    f"execution_status:{execution_status}. exit_status:{exit_status}. job_id:{self._job_id}")
        output_result_file(self._pid, self.response.dict(by_alias=True))

    def get_live_mount_script_param(self):
        return {
            'AppName': self.db_name,
            'InstanceName': self.db_instance,
            'UserName': self.db_user,
            'Password': self.db_user_pwd,
            'OracleHome': self.oracle_home,
            'AccessOracleHome': self.access_oracle_home,
            'AccessOracleBase': self.access_oracle_base,
            'ASMUserName': self.asm_user,
            'ASMPassword': self.asm_user_pwd,
            'Channel': self.channels,
            "DataPath": self.backup_repo,
            "LogPath": self.archive_repo,
            'MetaDataPath': self.meta_data_path,
            'recoverTarget': self.recover_target,
            'recoverPath': self.recover_path,
            'recoverOrder': self.recover_order,
            'ASMInstanceName': self.asm_sid_name,
            'dbType': self.db_type,
            'EncAlgo': self.enc_algo,
            'EncKey': self.enc_key,
            'pfilePID': self.p_file_pid,
            'recoverNum': self.recover_num,
            'startDb': self.is_start_db,
            'OracleInstallUser': self.oracle_user,
            'GridInstallUser': self.asm_install_user,
            'bctStatus': self.bct_status
        }

    def cancel_live_mount_ex(self):
        # 需要卸载本地目录
        ret = self.unmount_local_path()
        if not ret:
            return False
        logger.info(f"Cancel live mount success. pid:{self._pid} jobId:{self._job_id}.")
        return True

    def cancel_live_mount_progress(self):
        """
        取消即时挂载进度
        """

        result_file = os.path.join(ParamConstant.RESULT_PATH,
                                   f'{CacheRepoFilename.CANCEL_LIVE_MOUNT_PROGRESS}_{self._sub_job_id}')
        result_data = read_tmp_json_file(result_file)
        task_status = result_data.get(RestoreResultKey.TASK_STATUS, SubJobStatusEnum.RUNNING.value)
        if task_status == SubJobStatusEnum.RUNNING.value:
            # 任务未完成时将进度设置为50%
            progress = OracleProgress.PROGRESS_FIFTY
        else:
            logger.info(f'Cancel live mount job end, task_status:{task_status}, job_id:{self._job_id}, '
                        f'sub_job_id:{self._sub_job_id}.')
            progress = OracleProgress.PROGRESS_ONE_HUNDRED
            delete_file(result_file)

        sub_job_detail = SubJobDetails(taskId=self._job_id, subTaskId=self._sub_job_id, progress=progress,
                                       taskStatus=task_status)
        if task_status == SubJobStatusEnum.FAILED:
            error_code = result_data.get(RestoreResultKey.ERROR_CODE, "")
            contents = result_data.get(RestoreResultKey.ERROR_PARAM, "")
            log_detail = LogDetail(logInfo=ReportDBLabel.SUB_JOB_FALIED, logInfoParam=[self._sub_job_id],
                                   logLevel=DBLogLevel.ERROR.value, logDetail=error_code, logDetailInfo=[contents])
            sub_job_detail.extend_info = {'nodeId': get_host_sn()}
            sub_job_detail.log_detail = [log_detail]
        output_result_file(self._pid, sub_job_detail.dict(by_alias=True))

    def cancel_live_mount(self):
        """
        取消即时挂载
        """
        self.get_asm_info()
        script_param = self.get_cancel_live_mount_param()
        child = pexpect.spawn(
            f"sh {PluginPathConstant.SCRIPTS_PATH}/{RootCommand.NATIVE_CLIVE_MOUNT.value}"
            f" {PluginPathConstant.SCRIPTS_PATH.value} {self._sub_job_id} {len(script_param)}",
            encoding='utf-8')

        send_params_to_pexpect(script_param, child)
        child.timeout = None
        child.expect(pexpect.EOF)
        child.close()
        self.exit_status = child.exitstatus
        logger.info(f'Cancel_live_mount child.exitstatus:{self.exit_status}.')
        try:
            self.cancel_live_mount_ex()
        except Exception as exception_str:
            logger.error(f"Cancel_live_mount_ex:{str(exception_str)} pid:{self._pid} jobId{self._job_id}.")
        if self.exit_status != ScriptExitCode.SUCCESS:
            logger.info(f"Cancel Live_mount fail. job_id:{self._job_id}")
            self.output_result_info_to_file(ExecuteResultEnum.INTERNAL_ERROR.value, ErrorCode.INTERNAL_ERROR.value,
                                            SubJobStatusEnum.FAILED.value, self.exit_status,
                                            CacheRepoFilename.CANCEL_LIVE_MOUNT_PROGRESS)
        else:
            logger.info(f"Cancel Live_mount success. job_id:{self._job_id}")
            self.output_result_info_to_file(ExecuteResultEnum.SUCCESS.value, ErrorCode.SUCCESS.value,
                                            SubJobStatusEnum.COMPLETED.value, ScriptExitCode.SUCCESS,
                                            CacheRepoFilename.CANCEL_LIVE_MOUNT_PROGRESS)

    def get_cancel_live_mount_param(self):
        """
        获得取消即时挂载参数
        """

        return {
            'AppName': self.db_name,
            'InstanceName': self.db_instance,
            'UserName': self.db_user,
            'Password': self.db_user_pwd,
            'ASMUserName': self.asm_user,
            'ASMPassword': self.asm_user_pwd,
            'ASMInstanceName': self.asm_sid_name,
            'OracleInstallUser': self.oracle_user,
            'GridInstallUser': self.asm_install_user,
            'AccessOracleHome': self.access_oracle_home,
            'AccessOracleBase': self.access_oracle_base
        }

    def _parse_rac_node_info(self):
        host_sn = get_host_sn()
        try:
            node_info_list = json.loads(self._json_param.get("job", {}).get("targetObject", {}).
                                        get("extendInfo", {}).get('instances', ""))
        except json.JSONDecodeError:
            logger.error(f'json decode node_info exception , job_id:{self._job_id}.')
            return False
        try:
            node_info_list_protect = json.loads(self.first_copy_protect_object_extend_info_json.get('instances', ""))
        except json.JSONDecodeError:
            logger.error(f'json decode protect node_info exception , job_id:{self._job_id}.')
            return False
        logger.info(f"Parse_rac_deploy_type. jobid:{self._job_id}")
        if len(node_info_list) > len(node_info_list_protect):
            logger.error(f'target host node num({len(node_info_list)}) greater '
                         f'orginal node num({len(node_info_list_protect)}) , job_id:{self._job_id}.')
            return False
        for index_node, node_info in enumerate(node_info_list):
            if node_info.get('hostId') == host_sn:
                tmp_node_info = node_info_list_protect[index_node]
                self.db_instance = tmp_node_info.get('inst_name', "")
                # 如果指定了实例名，设置指定的实例名
                instance_name = get_instance_name_by_id(
                    self._json_param.get('job', {}).get('extendInfo', {}).get('instances', ''),
                    self._json_param.get('job', {}).get('targetObject', {}).get('id', ''))
                if instance_name:
                    self.db_instance = instance_name
                logger.info(f"Rac Node db_instance:{self.db_instance}. jobid:{self._job_id}")
                self.oracle_home = tmp_node_info.get('oracle_home', "")
                try:
                    self.asm_sid_name = json.loads(tmp_node_info.get('asm_info')).get('instName', "")
                except json.JSONDecodeError:
                    self.asm_sid_name = ""
                    logger.error(f'json decode asm_info exception , job_id:{self._job_id}.')
                    return False

        return True
